package util

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"gopkg.in/yaml.v3"
	"io"
	"net/url"
	"os"
	"path"
	"runtime"
	"time"
)

// LogSrv 定义全局公共变量
var LogSrv *ZapLogger
var LogRun *ZapLogger
var LogGM *ZapLogger

// Level 日志级别
type Level int

// 日志级别常量

/*
	FINEST：
	这是最低级别的日志，通常用于最详细的调试信息。在实际应用中，这个级别的日志可能会非常频繁，因此通常只在需要深入分析问题时启用。
	FINE：
	比 FINEST 级别稍高的日志，用于不太详细的调试信息。这个级别的日志可能包括一些关键变量的值或者程序的状态信息。
	DEBUG：
	用于调试的日志，通常包含有助于开发者理解程序运行状态的信息。这个级别的日志对于开发和测试阶段非常有用。
	TRACE：
	用于跟踪程序执行流程的日志。这个级别的日志可以帮助开发者了解程序的运行路径，特别是在复杂的逻辑中。
	INFO：
	提供一般信息的日志，通常用于正常的程序运行时。这个级别的日志对于监控程序状态和用户活动很有用。
	WARNING：
	表示潜在问题或异常情况的日志。这个级别的日志表明程序遇到了一些不严重的问题，但仍然可以继续运行。
	ERROR：
	表示严重问题，程序可能无法完成某些功能。这个级别的日志表明程序遇到了需要关注的错误。
	CRITICAL：
	这是最高级别的日志，表示非常严重的问题，可能导致程序崩溃或停止运行。这个级别的日志需要立即关注和处理。
*/

const (
	FINEST Level = iota
	FINE
	DEBUG
	TRACE
	INFO
	WARNING
	ERROR
	CRITICAL
)

// getLoggerRootPath 获取日志输出目录
func getLoggerRootPath(projectDir string) string {
	if runtime.GOOS == "windows" {
		return projectDir + "/log/"
	}
	return projectDir + "/log/"
}

// IsExistPath 判断日志输出目录是否存在
func IsExistPath(Path string) bool {
	_, err := os.Stat(Path)
	if err != nil {
		if os.IsExist(err) {
			return true
		}
		return false
	}
	return true
}

// zapConfigSet_ 全局私有变量实例化指针
var zapConfigSet_ *ZapConfigSet

func InitLogger(ProjectDir string) error {
	// 日志存在输出目录,不存在则创建
	LogDir := getLoggerRootPath(ProjectDir)
	if !IsExistPath(LogDir) {
		err := os.MkdirAll(LogDir, os.ModePerm)
		if err != nil {
			fmt.Printf("[logger] mkdir failed![%v]\n", err)
			return err
		}
	}

	// 读取日志配置文件里面数据
	data, err := os.ReadFile(path.Join(ProjectDir, "config/logger_config.yaml"))
	if err != nil {
		fmt.Printf("err: %v\n", err)
		return err
	}
	// 实例化指针
	zapConfigSet_ = &ZapConfigSet{}
	// 将二进制的文件流数据通过反序列给指针
	if err := yaml.Unmarshal(data, zapConfigSet_); err != nil {
		fmt.Printf("err: %v\n", err)
		return err
	}
	// 判断是否开启打印
	/*
		os.O_SYNC :这个标志表示在每次写操作后，数据将被同步写入存储设备。这意味着每次写入都会等待数据实际写入磁盘，而不是仅仅写入文件系统的缓存。
		os.O_CREATE:这个标志表示如果文件不存在，则创建一个新文件。如果文件已经存在，则忽略此标志。
		os.O_WRONLY:这个标志表示以只写方式打开文件。如果文件不存在，将返回错误。
	*/
	if zapConfigSet_.Console.Dup {
		// 判断日志输出文件路径是否存在
		if len(zapConfigSet_.Console.StdoutOutputPath) > 0 {
			f, _ := os.OpenFile(path.Join(ProjectDir, zapConfigSet_.Console.StdoutOutputPath),
				os.O_SYNC|os.O_CREATE|os.O_WRONLY, 0775)
			os.Stdout = f
		}
		if len(zapConfigSet_.Console.StderrOutputPath) > 0 {
			f, _ := os.OpenFile(path.Join(ProjectDir, zapConfigSet_.Console.StderrOutputPath),
				os.O_SYNC|os.O_CREATE|os.O_WRONLY, 0775)
			os.Stderr = f
		}
	}

	// 循环获取zapConfigSet_.Files的值
	for _, it := range zapConfigSet_.Files {
		if it.Enable == true {
			logger := NewZapLogger(it)
			if it.Category == "server" {
				LogSrv = logger
			} else if it.Category == "run" {
				LogRun = logger
			} else if it.Category == "gm" {
				LogGM = logger
			}
			logger.Info("[Logger]: Create logger success, Category=%s", it.Category)
		}
	}
	return nil
}

// ZapConfigSet 使用zap来设置日志配置
type ZapConfigSet struct {
	// 打印数据结构体
	Console struct {
		Dup              bool   `json:"dup" yaml:"dup"`
		StderrOutputPath string `json:"stderrOutputPath" yaml:"stderrOutputPath"`
		StdoutOutputPath string `json:"stdoutOutputPath" yaml:"stdoutOutputPath"`
	} `json:"console" yaml:"console"`
	Files []*ZapConfig `json:"files" yaml:"files"`
}

// GetConfig 获取ZapConfigSet的配置数据
func (zc *ZapConfigSet) GetConfig(category string) *ZapConfig {

	for _, it := range zc.Files {
		if it.Category == category {
			return it
		}
	}
	return nil
}

// -------------------------------使用zap的logger功能，做一些定制化的修改---------------------------------------

// ZapConfig 通过继承zap.Config和添加lumberjack的配置项，来组合一个大的配置项
type ZapConfig struct {
	zap.Config `yaml:",inline"`
	Filename   string `json:"filename" yaml:"filename"`     // 日志文件夹
	MaxSize    int    `json:"maxSize" yaml:"maxSize"`       // 但日志最大容量(单位MB)
	MaxBackups int    `json:"maxBackups" yaml:"maxBackups"` // 保留旧文件最大数量
	MaxAge     int    `json:"maxAge" yaml:"maxAge"`         // 旧文件最多保留几天
	Compress   bool   `json:"compress" yaml:"compress"`     // 是否研所/归档旧文件

	Enable   bool   `json:"enable" yaml:"enable"`     // 是否启用该日志配置
	Category string `json:"category" yaml:"category"` // 日志输出策略标识
}

// MySink 构建zap的Sink，sink包含WriteSyncerzapcore.WriteSyncer和io.Closer两部分接口，具体参考zap源码
type MySink struct {
	io.Writer
}

// Sync 实现WriteSyncerzapcore.WriteSyncer 方法
func (m *MySink) Sync() error {
	return nil
}

// Close 这是一个 Close 方法的实现，它满足 io.Closer 接口的要求。Close 方法在这里总是返回 nil，表示没有错误。在实际应用中，这个方法应该包含释放资源的逻辑，比如关闭文件或者网络连接。
func (m *MySink) Close() error {
	return nil
}

// NewMySink 实例化设置：自定义输出到滚动文件
func NewMySink(url *url.URL) (zap.Sink, error) {
	config := zapConfigSet_.GetConfig(url.Host)
	fmt.Println("FileName:", config.Filename)
	lumberjackLogger := &lumberjack.Logger{
		Filename:   config.Filename,   // 日志文件
		MaxSize:    config.MaxSize,    // 单文件最大容量(单位MB)
		MaxBackups: config.MaxBackups, // 保留旧文件的最大数量
		MaxAge:     config.MaxAge,     // 旧文件最多保存几天
		Compress:   config.Compress,   // 是否压缩/归档旧文件
	}
	return &MySink{lumberjackLogger}, nil
}

// NewMyEncoder 自定义日志格式化
func NewMyEncoder(cfg zapcore.EncoderConfig) (zapcore.Encoder, error) {
	cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
		enc.AppendString(t.Format("2006-01-02 15:04:05"))
	}
	enc := zapcore.NewConsoleEncoder(cfg)
	return enc, nil
}

// NewZapLogger 实例化参数设置
func NewZapLogger(config *ZapConfig) *ZapLogger {
	// 自定义输出日志到滚动文件
	zap.RegisterSink("mySink", NewMySink)
	// 注册编码器 命名必须为和配置文件保持一致否则会编码器注册失败
	zap.RegisterEncoder("myEncoder", NewMyEncoder)

	// zap.Logger
	logger := zap.Must(config.Build()).WithOptions(zap.AddCallerSkip(2))
	defer logger.Sync()
	return &ZapLogger{base: logger}
}

// -------------------------------封装接口--------------------------------------------------------------------------

// ZapLogger zap.Logger 是 zap 日志库中的核心类型，用于创建和发送日志条目
type ZapLogger struct {
	base *zap.Logger
}

// SetLevel 动态更改日志级别
func (zl *ZapLogger) SetLevel(level Level) {
	var zapLevel zapcore.LevelEnabler
	switch level {
	case DEBUG:
		zapLevel = zap.DebugLevel
	case INFO:
		zapLevel = zap.InfoLevel
	case WARNING:
		zapLevel = zap.WarnLevel
	case ERROR:
		zapLevel = zap.ErrorLevel
	case CRITICAL:
		zapLevel = zap.FatalLevel
	default:
		zapLevel = zap.InfoLevel
	}
	zl.base = zl.base.WithOptions(zap.IncreaseLevel(zapLevel))
}

// log message with Sprint, Sprintf, or neither.
// io写入日志文件
func (zl *ZapLogger) log(level zapcore.Level, template string, fmtArgs []interface{}) {
	// 如果日志级别小于异常严重的级别或者
	// && !zl.base.Core().Enabled(level)：然后，代码检查 zl.base.Core().Enabled(level) 的返回值。
	// zl.base 是一个 zap.Logger 实例，Core() 方法返回这个实例的核心，Enabled(level) 方法检查是否启用了指定级别的日志记录。如果 Enabled(level) 返回 false，意味着当前配置不允许记录 level 级别的日志。
	if level < zap.DPanicLevel && !zl.base.Core().Enabled(level) {
		return
	}
	// 定义多个参数
	var fields []zapcore.Field
	msg := zl.getMessage(template, fmtArgs)
	if ce := zl.base.Check(level, msg); ce != nil {
		ce.Write(fields...)
	}
}

// getMessage 封装获取写入日志文件的
func (zl *ZapLogger) getMessage(template string, fmtArgs []interface{}) string {
	// 判断有没有额外参数
	if len(fmtArgs) == 0 {
		return template
	}
	// 判断 消息不为空 情况
	if template != "" {
		return fmt.Sprintf(template, fmtArgs...)
	}
	// 判断只有一个参数情况
	if len(fmtArgs) == 1 {
		if str, ok := fmtArgs[0].(string); ok {
			return str
		}
	}
	return fmt.Sprint(fmtArgs...)
}

// ----------------------------------------------- 封装日志信息------------------------------------------------------------

// Debug 级别
func (zl *ZapLogger) Debug(template string, args ...interface{}) {
	zl.log(zap.DebugLevel, template, args)
}

// Info 级别
func (zl *ZapLogger) Info(template string, args ...interface{}) {
	zl.log(zap.InfoLevel, template, args)
}

// Warn 级别
func (zl *ZapLogger) Warn(template string, args ...interface{}) {
	zl.log(zap.WarnLevel, template, args)
}

// Error 级别
func (zl *ZapLogger) Error(template string, args ...interface{}) {
	zl.log(zap.ErrorLevel, template, args)
}
